package de.ba_rm.java.password.checker;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.prefs.Preferences;

import javax.swing.JOptionPane;
import javax.xml.bind.JAXBException;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;

import de.ba_rm.java.password.checker.logic.PasswordChecker;
import de.ba_rm.java.password.checker.logic.config.Rule;
import de.ba_rm.java.password.checker.logic.config.RuleIO;
import de.ba_rm.java.password.checker.view.ConfigView;
import de.ba_rm.java.password.checker.view.View;

public class Controller {
	
	private View view;
	
	private ConfigView configView;
	
	private PasswordChecker checker = new PasswordChecker();
	
	private String lastConfig;

	/**
	 * default Rule
	 */
	public static final Rule RULE;
	static {
		RULE = new Rule();
		RULE.setMinLength(8);
		RULE.setName(Rule.NAME_DEFAULT);
		RULE.setNumCase(2);
		RULE.setNumLowerCase(1);
		RULE.setNumUpperCase(1);
		RULE.setNumNumbers(2);
		RULE.setNumSpecial(1);
		RULE.setNumSpecialBetweenChar(1);
	}
	
	/**
	 * default c-tor
	 */
	public Controller() {
		final Preferences preferences = Preferences.userNodeForPackage(PasswordChecker.class);
		this.lastConfig = preferences.get("lastConfig", null);
		try {
			if (!RuleIO.nameAlreadyExists(lastConfig) && !RuleIO.nameAlreadyExists(Rule.NAME_DEFAULT)) {
				try {
					RuleIO.save(RULE);
				} catch (JAXBException e) {
					throw new RuntimeException(e);
				}
				this.lastConfig = Rule.NAME_DEFAULT;
			} else if (!RuleIO.nameAlreadyExists(lastConfig)) {
				this.lastConfig = Rule.NAME_DEFAULT;
			}
			
			
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
		
		view = new View(this);
		view.getConfigLabel().setText(lastConfig);
	}
	
	/**
	 * Beendet die Anwendung
	 */
	public void actionClose() {
		final Preferences preferences = Preferences.userNodeForPackage(PasswordChecker.class);
		preferences.put("lastConfig", lastConfig);
		view.dispose();
	}
	
	/**
	 * Startet die Konfigurations-Ansicht
	 */
	public void actionConfigView() {
		configView = new ConfigView(this, this.view);
		configView.getRuleComboBox().setSelectedItem(lastConfig);
	}
	
	/**
	 * Prüft ein gegebenes Passwort gegen die konigurierten Regeln.
	 */
	public void actionValidate() {
		final String password = view.getPasswordTextField().getText();
		if (StringUtils.isNotBlank(lastConfig)) {
			final Rule rule = RuleIO.load(lastConfig);
			this.checker = new PasswordChecker(rule);
		} 
		
		final List<String> errorMessages = this.checker.checkPassword(password);
		
		if (errorMessages.isEmpty()) {
			JOptionPane.showMessageDialog(this.view, Messages.getString("View.14"), Messages.getString("View.15"), JOptionPane.INFORMATION_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$
		} else {
			final StringBuilder messageBuilder = new StringBuilder();
			for (String m : errorMessages) {
				messageBuilder.append(m).append("<br>");
			}
			final String validationUnsuccessful = Messages.getString("View.16", messageBuilder.toString());
			JOptionPane.showMessageDialog(this.view, validationUnsuccessful, Messages.getString("View.17"), JOptionPane.WARNING_MESSAGE); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}
	
	/**
	 * Speichert einen neuen oder geänderten Regelsatz ab.
	 * @throws IOException Wenn der Regelname leer ist
	 * 
	 */
	public void actionSave() throws IOException {
		final String fileName = this.configView.getNameTextField().getText();
		if (StringUtils.isBlank(fileName)) {
			throw new IOException("Regelname darf nicht leer sein!");
		}
		
		final String cleanFileName = fileName.replaceAll("\\\\\\/:\\*\\?\"<>\\|", "").trim();
		
		if (configView.getRuleComboBox().getSelectedIndex() == 0) {
			final String[] names = RuleIO.loadRulesetNames();
			if (CollectionUtils.exists(Arrays.asList(names), new Predicate() {
				@Override
				public boolean evaluate(Object object) {
					return object.toString().equalsIgnoreCase(FilenameUtils.getBaseName(cleanFileName));
				}
			})) {
				final int result = JOptionPane.showConfirmDialog(configView, Messages.getString("ConfigView.13"), Messages.getString("ConfigView.14"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
				switch (result) {
				case JOptionPane.YES_OPTION:
					convertAndSave(cleanFileName);
					break;
				case JOptionPane.NO_OPTION:
				default:
					break;
				}
			} else {
				convertAndSave(cleanFileName);
			}
		} else {
			convertAndSave(cleanFileName);
		}
	}

	/**
	 * Übernimmt die Eingaben und speichert sie ein Regel-Objekt um sie zu persistieren.
	 * @param fileName
	 */
	private void convertAndSave(final String fileName) {
		final Rule rule = new Rule();
		rule.setName(fileName);
		rule.setMinLength(Integer.parseInt(this.configView.getLengthSpinner().getValue().toString()));
		rule.setNumCase(Integer.parseInt(this.configView.getCaseSpinner().getValue().toString()));
		rule.setNumLowerCase(Integer.parseInt(this.configView.getLowerCaseSpinner().getValue().toString()));
		rule.setNumUpperCase(Integer.parseInt(this.configView.getUpperCaseSpinner().getValue().toString()));
		rule.setNumSpecial(Integer.parseInt(this.configView.getSpecialSpinner().getValue().toString()));
		rule.setNumSpecialBetweenChar(Integer.parseInt(this.configView.getSpecialBetweenCharSpinner().getValue().toString()));
		rule.setNumNumbers(Integer.parseInt(this.configView.getNumbersSpinner().getValue().toString()));
		
		try {
			RuleIO.save(rule);
			this.configView.loadComboBox(rule.getName());
		} catch (JAXBException e) {
			throw new RuntimeException(e);
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Lädt die aktuell ausgewählte Rule. 
	 */
	public void actionApply() {
		final File file = (File) configView.getRuleComboBox().getSelectedItem();
		final Rule rule = RuleIO.load(file);
		this.view.getConfigLabel().setText(rule.getName());
		this.configView.dispose();
		this.lastConfig = rule.getName();
	}

	/**
	 * 
	 * @param file
	 */
	public void actionRefresh(final File file) {
		final Rule rule = RuleIO.load(file);
		this.configView.getNameTextField().setText(rule.getName());
		this.configView.getLengthSpinner().setValue(rule.getMinLength());
		this.configView.getCaseSpinner().setValue(rule.getNumCase());
		this.configView.getLowerCaseSpinner().setValue(rule.getNumLowerCase());
		this.configView.getUpperCaseSpinner().setValue(rule.getNumUpperCase());
		this.configView.getSpecialSpinner().setValue(rule.getNumSpecial());
		this.configView.getSpecialBetweenCharSpinner().setValue(rule.getNumSpecialBetweenChar());
		this.configView.getNumbersSpinner().setValue(rule.getNumNumbers());
	}
	
	/**
	 * 
	 * @return
	 */
	public String getLastConfig() {
		return this.lastConfig;
	}

	/**
	 * 
	 */
	public void initDefault() {
		configView.getNameTextField().setText(StringUtils.EMPTY);
		configView.getNameTextField().setEnabled(true);
		configView.getNameTextField().requestFocus();
		configView.setComboBoxDefaults(RULE);
	}
	
	/**
	 * 
	 * @param pass
	 */
	public void actionCalculateHashAndEntropy(final String pass) {
		if (StringUtils.isNotBlank(pass)) {
			final int entropy = PasswordChecker.calculateEntropy(pass);
			view.getPanel().setEntropy(entropy);
			view.getEntropyLabel().setText(String.valueOf(entropy) + Messages.getString("View.8")); //$NON-NLS-1$
			
			final String md5 = DigestUtils.md5Hex(pass);
			final String sha1 = DigestUtils.sha1Hex(pass);
			final String sha256 = DigestUtils.sha256Hex(pass);
			final String sha512 = DigestUtils.sha512Hex(pass);
			view.getMd5TextField().setText(md5.toUpperCase());
			view.getSha1TextField().setText(sha1.toUpperCase());
			view.getSha256TextField().setText(sha256.toUpperCase());
			view.getSha512TextField().setText(sha512.toUpperCase());
		} else {
			view.getEntropyLabel().setText(View.ENTROPY_LABEL_DEFAULT);
			view.getPanel().setEntropy(0);
			view.getMd5TextField().setText(StringUtils.EMPTY);
			view.getSha1TextField().setText(StringUtils.EMPTY);
			view.getSha256TextField().setText(StringUtils.EMPTY);
			view.getSha512TextField().setText(StringUtils.EMPTY);
		}
	}
}
